package options

import (
	"file-cleaner/internal/file_cleaner/query_handler"
	"file-cleaner/internal/pkg/server"
	"github.com/marmotedu/errors"
	"github.com/spf13/pflag"
	"os"
	"strings"
	"time"
)

const (
	AccessTime = "atime"
	ModifyTime = "mtime"
	CTime      = "ctime"

	timeLayoutStr     = "2006-01-02 15:04:05"
	defaultTimeBefore = "1970-01-01 00:00:00"
)

type QueryOptions struct {
	BaseDir       string   `json:"base-dir" mapstructure:"base-dir"`
	Excludes      []string `json:"excludes"  mapstructure:"excludes"`
	MatchTimeType string   `json:"match-time-type" mapstructure:"match-time-type"`
	TimeAfter     string   `json:"time-after"      mapstructure:"time-after"`
	TimeBefore    string   `json:"time-before"     mapstructure:"time-before"`
	IsCascade     bool     `json:"is-cascade"      mapstructrue:"is-cascade"`
}

func NewQueryOptions() *QueryOptions {
	return &QueryOptions{
		TimeAfter:  time.Now().Format(timeLayoutStr),
		TimeBefore: defaultTimeBefore,
		IsCascade:  true,
	}
}

func (q *QueryOptions) AddFlags(fs *pflag.FlagSet) {
	fs.StringVar(&q.BaseDir, "query.base-dir", q.BaseDir, ""+
		"Base file directory for querying files.")

	fs.StringSliceVar(&q.Excludes, "query.excludes", q.Excludes, ""+
		"Specify excluded files or directories."+
		" Supports absolute paths and relative paths relative to the query base directory."+
		" (File path wildcard matching is currently not supported.)")

	fs.StringVar(&q.MatchTimeType, "query.match-time-type", q.MatchTimeType, ""+
		"Set the timestamp type to match the specified file timestamp."+
		" Selection range: "+AccessTime+", "+ModifyTime+", "+CTime+"."+
		" "+CtimeComment)

	fs.StringVar(&q.TimeAfter, "query.time-after", q.TimeAfter, ""+
		"File filter by time After this, Format: `'YYYY-MM-DD hh24:mi:ss'`.")

	fs.StringVar(&q.TimeBefore, "query.time-before", q.TimeBefore, ""+
		"File filter by time Before this, Format: `'YYYY-MM-DD hh24:mi:ss'`.")

	fs.BoolVar(&q.IsCascade, "query.is-cascade", q.IsCascade, ""+
		"Whether the cascade for deletion.")
}

func (q *QueryOptions) Validate() []error {
	var errs []error

	// validate base-dir
	if len(strings.TrimSpace(q.BaseDir)) == 0 {
		errs = append(errs, errors.New("--query.base-dir cannot be empty."))
	} else {
		dirEntry, err := os.Stat(q.BaseDir)
		if err != nil {
			errs = append(errs, errors.Errorf("--query.base-dir must be a dir which is readable. Read dir failed: %+v.", err))
		} else {
			if !dirEntry.IsDir() {
				errs = append(errs, errors.New("--query.base-dir must be a dir which is readable. The dir `"+q.BaseDir+"` is not a dir."))
			}
		}
	}

	// validate match-time-type
	if len(strings.TrimSpace(q.MatchTimeType)) > 0 {
		switch q.MatchTimeType {
		case AccessTime, ModifyTime, CTime:
			break
		default:
			errs = append(errs, errors.New("--query.match-time-type "+
				"selection range: "+AccessTime+", "+ModifyTime+", "+CTime+"."))
		}
	}

	// validate time-after
	if len(strings.TrimSpace(q.TimeAfter)) > 0 {
		_, err := time.Parse(timeLayoutStr, q.TimeAfter)
		if err != nil {
			errs = append(errs, errors.Errorf("--query.time-after format is invalid: %+v. The format is `YYYY-MM-DD hh24:mi:ss`.", err))
		}
	}

	// validate time-before
	if len(strings.TrimSpace(q.TimeBefore)) > 0 {
		_, err := time.Parse(timeLayoutStr, q.TimeBefore)
		if err != nil {
			errs = append(errs, errors.Errorf("--query.time-before format is invalid: %+v. The format is `YYYY-MM-DD hh24:mi:ss`.", err))
		}
	}

	return errs
}

func (q *QueryOptions) ApplyTo(c *server.Config) error {
	var err error

	c.Query.BaseDir = q.BaseDir
	c.Query.Excludes = q.Excludes
	c.Query.IsCascade = q.IsCascade
	//if c.Query.QueryHandlerChain == nil {
	//	c.Query.QueryHandlerChain = make([]server.QueryHandlerBuilder, 0)
	//}

	timeAfter, err := time.Parse(timeLayoutStr, q.TimeAfter)
	if err != nil {
		return err
	}
	timeBefore, err := time.Parse(timeLayoutStr, q.TimeBefore)
	if err != nil {
		return err
	}

	switch q.MatchTimeType {
	case AccessTime:
		c.Query.QueryHandlerChain = append(c.Query.QueryHandlerChain, server.QueryHandlerBuilder{
			Builder:    query_handler.QueryByATime,
			TimeAfter:  timeAfter,
			TimeBefore: timeBefore,
		})
	case ModifyTime:
		c.Query.QueryHandlerChain = append(c.Query.QueryHandlerChain, server.QueryHandlerBuilder{
			Builder:    query_handler.QueryByMTime,
			TimeAfter:  timeAfter,
			TimeBefore: timeBefore,
		})
	case CTime:
		c.Query.QueryHandlerChain = append(c.Query.QueryHandlerChain, server.QueryHandlerBuilder{
			Builder:    query_handler.QueryByCTime,
			TimeAfter:  timeAfter,
			TimeBefore: timeBefore,
		})
	}

	return nil
}
